设计模式(四)状态模式 & 代理模式

  1. 状态模式
    1. 应用场景
    2. 方案一
    3. 方案二
    4. 总结
  2. 代理模式
    1. 应用场景
    2. 总结

状态模式

应用场景

制作一部糖果机,拥有投钱、退钱、检查糖果、发放糖果的程序。

方案一

在不同的操作下,先根据当前所在的状态作出对应的响应

public class GumballMachine1 {
    final static int SOLD_OUT = 0;
    final static int NO_QUARTER = 1;
    final static int HAS_QUARTER = 2;
    final static int SOLD = 3;

    int state = SOLD_OUT;
    int count = 0;

    public GumballMachine(int count) {
        this.count = count;
        if (count > 0) {
            state = NO_QUARTER;
        }
    }

    public void insertQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("You can't insert another quarter");
        } else if (state == NO_QUARTER) {
            state = HAS_QUARTER;
            System.out.println("You inserted a quarter");
        } else if (state == SOLD_OUT) {
            System.out.println("You can't insert a quarter, the machine is sold out");
        } else if (state == SOLD) {
            System.out.println("Please wait, we're already giving you a gumball");
        }
    }
    
    public void ejectQuarter() {
        if (state == HAS_QUARTER) {
            System.out.println("Quarter returned");
            state = NO_QUARTER;
        } else if (state == NO_QUARTER) {
            System.out.println("You haven't inserted a quarter");
        } else if (state == SOLD) {
            System.out.println("Sorry, you already turned the crank");
        } else if (state == SOLD_OUT) {
            System.out.println("You can't eject, you haven't inserted a quarter yet");
        }
    }

    public void turnCrank() {
        if (state == SOLD) {
            System.out.println("Turning twice doesn't get you another gumball");
        } else if (state == NO_QUARTER) {
            System.out.println("You turned but there's no quarter");
        } else if (state == SOLD_OUT) {
            System.out.println("Your turned, but there are no gumballs");
        } else if (state == HAS_QUARTER) {
            System.out.println("You turned...");
            state = SOLD;
            dispense();
        }
    }

    public void dispense() {
        if (state == SOLD) {
            System.out.println("A gumball comes rolling out the slot");
            count = count - 1;
            if (count == 0) {
                System.out.println("Oops, out of gumballs");
                state = SOLD_OUT;
            } else {
                state = NO_QUARTER;
            }
        } else if (state == NO_QUARTER) {
            System.out.println("You need to pay first");
        } else if (state == SOLD_OUT) {
            System.out.println("No gumball dispensed");
        } else if (state == HAS_QUARTER) {
            System.out.println("No gumball dispensed");
        }
    }
}

基于方案一,当这个糖果机有新的需求,需要多加入一个“赢家”的状态,就要在每个方法中加入一个新的条件判断来处理,业务逻辑也会更加混乱。

方案二

定义一个状态接口,将每个具体的状态封装成一个类实现该接口

public interface State {
    public void insertQuarter();
    public void ejectQuarter();
    public void turnCrank();
    public void dispense();
}

public class NoQuarterState implements State {
    GumballMachine2 gumballMachine;

    public NoQuarterState(GumballMachine2 gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    public void insertQuarter() {
        System.out.println("You inserted a quarter");
        gumballMachine.setState(gumballMachine.getHasQuarterState()); //这样设置是为了减轻对其它状态类的依赖
    }

    public void ejectQuarter() {
        System.out.println("You haven't inserted a quarter");
    }

    public void turnCrank() {
        System.out.println("You turned but there's no quarter");
    }

    public void dispense() {
        System.out.println("You need to pay first");
    }
}

public class WinnerState implements State {
    public void dispense() {
        System.out.println("You're a winner, you get two gumballs for your quarter");
        gumballMachine.releaseBall();
        if (gumballMachine.getCount() == 0) {
            gumballMachine.setState(gumballMachine.getSoldOutState());
        } else {
            gumballMachine.releaseBall();
            if (gumballMachine.getCount() > 0) {
                gumballMachine.setState(gumballMachine.getNoQuarterState());
            } else {
                System.out.println("Oops, out of gumballs");
                gumballMachine.setState(gumballMachine.getSoldOutState());
            }
        }
    }

    //... 其它接口方法实现报错信息,和实例对象
}

public class HasQuarterState implements State {
    Random randomWinner = new Random(System.currentTimeMillis());

    public void turnCrank() {
        System.out.println("You turned...");
        int winner = randomWinner.nextInt(10);
        if ((winner == 0) && (gumballMachine.getCount() > 1)) {
            gumballMachine.setState(gumballMachine.getWinnerState());
        } else {
            gumballMachine.setState(gumballMachine.getSoldState());
        }
    }

    //... 其它接口方法实现报错信息,和实例对象
}

//同理,再实现其它状态类。

public class GumballMachine2 {
    State soldOutState;
    State noQuarterState;
    State hasQuarterState;
    State soldState;
    State winnerState;

    State state = soldOutState;
    int count = 0;

    public GumballMachine(int numberGumballs) {
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);

        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = noQuarterState;
        }
    }

    public void insertQuarter() {
        state.insertQuarter();
    }
    
    public void ejectQuarter() {
        state.ejectQuarter();
    }

    public void turnCrank() {
        state.turnCrank();
        state.dispense();
    }

    void setState(State state) {
        this.state = state;
    }

    int getCount() {
        return this.count;
    }

    void releaseBall() {
        System.out.println("A gumball comes rolling out the slot");
        if (count != 0) {
            count = count - 1;
        }
    }
}

经过方案二的优化后,将状态封装成独立的类,并将动作委托到代表当前状态的对象。而整体代码则清晰简洁了,代码逻辑的划分更到位(局部化),更改代码方便且不易影响其它状态。

总结

状态模式允许对象基于内部状态而拥有不同的行为,在内部状态改变时改变它的行为,对象看起来好像修改了它的类。但状态模式是通过增加类的数目代价来获取弹性的。

延伸,和策略模式对比,主要是他们的意图不一样(他们的类图是一致的),策略模式通常会用行为或算法来配置Context类,状态模式允许Context随着状态改变而改变行为。

代理模式

应用场景

对糖果机的销售情况做监控

首先解释一下,这里的代理和Objective-C中所说的代理(Delegate)是有区别的,在下面的延伸部分再做详细说明。

 //服务端的实现
import java.rmi.*;
import java.io.*;
import java.rmi.server.*;

public interface GumballMachineRemote extends Remote {
    public int getCount() throws RemoteException; //因为是远程操作,所以得声明异常
    public String getLocation() throws RemoteException;
    public State getState() throws RemoteException;
}

public interface State extends Serializable { //只有扩展了序列化接口才可以在网络上传送
    public void insertQuarter();
    public void ejectQuarter();
    public void turnCrank();
    public void dispense();
}

public class NoQuarterState implements State {
    transient GumballMachine3 gumballMachine; //为了不让GumballMachine加入到序列化,使用transient关键字修饰各个State实现类中的该实例变量

    //... 其它方法的实现
}

//让GumballMachine继承UnicastRemoteObject,使其成为远程服务
public class GumballMachine3 extends UnicastRemoteObject implements GumballMachineRemote {
    public GumballMachine(String location, int numberGumballs) throws RemoteException {
        //...
    }

    //... GumballMachineRemote远程接口的实现,rmi系统会自动针对这些接口生成可被远程访问的方法
}

//客户端的实现
public class GumballMonitor {
    GumballMachineRemote machine;

    public GumballMonitor(GumballMachineRemote machine) {
        this.machine = machine;
    }

    public void report() {
        try {
            System.out.println(machine.getLocation());
            System.out.println(machine.getCount());
            System.out.println(machine.getState());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

//测试程序
public class test1 {
    void test() throws Exception {
        try {
            GumballMachineRemote machine = (GumballMachineRemote) Naming.lookup("rmi://xx.xx.com/gumballmachine");
            GumballMonitor monitor = new GumballMonitor(machine);
            monitor.report();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里,GumballMachineRemote就是一个远程代理。

总结

代理模式为另一个对象提供一个替身或占位符以访问这个对象,以便控制或管理访问。

延伸,代理还有很多变体,例如虚拟代理(控制访问实例化开销大的对象)、保护代理(基于调用者控制对象方法的访问)、防火墙代理、缓存代理等。

代理模式与装饰者模式的区别,装饰者模式只能装饰点缀,不会实例化任何对象,它是为对象加上行为,而代理则是通过创建代理对象控制访问。

Objective-C中的代理是指实现一个协议(接口)的对象,通过委托这个对象去执行协议中的方法,而无需关系该对象是什么,但它并没有是谁的替身或占位符的意思,也不是用于控制访问。

Java内置的代理支持可建立动态代理(反射),将调用分配到所选的处理器,下面再提供一个用动态代理的实现保护代理例子

public interface PersonBean {
    void setName(String name);//想要控制别人不能设置,自己能设置
    void setHotOrNotRating(int rating);//想要控制自己不能设置,别人能设置
}

public class PersonBeanImpl implements PersonBean {
    String name;
    int rating;

    public void setName(String name) {
        this.name = name;
    }

    public void setHotOrNotRating(int rating) {
        this.rating += rating;
    }
}

//import java.lang.reflect.*
public class OwnerInvocationHandler implements InvocationHandler {
    PersonBean person;

    public OwnerInvocationHandler(PersonBean person) {
        this.person = person;
    }

    //动态代理的任何接口方法被调用时,会调用该invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
        try {
            if (method.getName().startWith("get")) {
                return method.invoke(person,args);
            } else if (method.getName().equals("setHotOrNotRating")) {
                throw new IllegalAccessException();
            } else if (method.getName().startWith("set")) {
                return method.invoke(person,args);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

public class NoOwnerInvocationHandler implements InvocationHandler {
    PersonBean person;

    public OwnerInvocationHandler(PersonBean person) {
        this.person = person;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
        try {
            if (method.getName().startWith("get")) {
                return method.invoke(person,args);
            } else if (method.getName().equals("setHotOrNotRating")) {
                return method.invoke(person,args); //与OwnerInvocationHandler相反
            } else if (method.getName().startWith("set")) {
                throw new IllegalAccessException(); //与OwnerInvocationHandler相反
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

//这样就无需额外定义代理类,在使用时通过下面的Java内置代理支持方法获取动态的代理
public class test2 {
    PersonBean getOwnerProxy(PersonBean person) {
        return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(),person.getClass().getInterfaces(),new OwnerInvocationHandler(person));
    }
    PersonBean getNoOwnerProxy(PersonBean person) {
        return (PersonBean) Proxy.newProxyInstance(person.getClass().getClassLoader(),person.getClass().getInterfaces(),new NoOwnerInvocationHandler(person));
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 mingfungliu@gmail.com

文章标题:设计模式(四)状态模式 & 代理模式

文章字数:2.1k

本文作者:Mingfung

发布时间:2019-02-12, 13:12:00

最后更新:2019-02-12, 14:37:10

原始链接:http://blog.ifungfay.com/设计模式/设计模式(四)状态模式 & 代理模式/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

宝贝回家